﻿//////////////////////////////////////////////////////////////////////////////
//
//  Copyright © 1998-2024 Glodon Company Limited.  All rights reserved.
//
//  Use of this software is subject to the terms of the Glodon license
//  agreement provided at the time of installation or download, or which
//  otherwise accompanies this software in either electronic or hard copy form.  
//
//////////////////////////////////////////////////////////////////////////////

// Reviewed

#pragma once
#include "GcmpRenderingInterface.h"
#include "Box3d.h"
#include "GbmpTypeDefs.h"
#include "Color.h"

namespace gcmp
{
    class ICanvas;
    class ICamera;
    class IModelView;
    class ICanvasOptions;
    class ICameraChangedEvent;
    class ISceneChangedEvent;
    enum class GraphicsRenderLayer;
    class IDiscretizedData;
    class UniIdentity;
    class ICanvasEvent;
    class RenderContext;
    class TiltBox3d;
    class IHaloedGapManager;
    class IOneAnimationFrame;

    enum class CameraEventType : Int32
    {
        Pan = 0,    ///< 平移事件
        Zoom = 1,  ///< 缩放事件
        Rotation = 2  ///< 旋转事件
    };

    /// \brief 录制视频参数设置
    ///
    /// \param fileName 保存文件名，全路径
    /// \param resolutionWidth 分辨率宽，缺省为800，分辨率宽和高建议优先使用800×600,1280×720,1920×1080,2080×1080
    /// \param resolutionHeight 分辨率高，缺省为600，分辨率宽和高建议优先使用800×600,1280×720,1920×1080,2080×1080
    /// \param frameRate 帧率，缺省为24，由于编码解码器的不同，设置不合理可能导致录屏失败，建议优先尝试24(film)、25(PAL)、30(NTSC)
    /// \param isLossless 是否为无损，缺省为无损
    /// \param isStrech 是否拉伸， 默认为true拉伸
    struct RecordSetting
    {
        RecordSetting(const std::wstring& fileName, int resolutionWidth = 800, int resolutionHeight = 600, unsigned int frameRate = 24, bool isLossless = true, bool isStrech = true)
            : FileName(fileName)
            , ResolutionWidth(resolutionWidth)
            , ResolutionHeight(resolutionHeight)
            , FrameRate(frameRate)
            , IsLossless(isLossless)
            ,IsStrech(isStrech)
        {
        }
        std::wstring FileName;  //保存文件名，全路径
        int ResolutionWidth;    //分辨率宽
        int ResolutionHeight;   //分辨率高
        unsigned int FrameRate; //帧率，由于编码解码器的不同，设置不合理可能导致录屏失败，建议优先尝试24(film)、25(PAL)、30(NTSC)
        bool IsLossless;        //是否为无损
        bool IsStrech;          //是否拉伸（默认为拉伸）
    };

    /// \brief光照方案
    enum class LightingScheme : Int32
    {
        Default,            ///默认光照
        HeadLamp,           ///头灯
        CosmicAmbientLamp,  ///天空散射灯
        CadLighting,        ///Cad灯光
        ShadowLighting     ///阴影灯光
    };

    typedef std::function<void()> GISLODLoadFinishCallBack;

    /// \brief 画布
    ///
    /// 画布是View的内容（即Scene）
    class GCMP_RENDERING_INTERFACE_EXPORT ICanvas
    {
    public:
        virtual ~ICanvas();

        /// \brief 创建视图画布
        /// \param hWnd 窗口句柄
        /// \param pModelView 模型视图
        /// \param pParentCanvas 父Canvas指针，可以为空，子Canvas会共享父Canvas的场景
        /// \return 新创建的画布
        static OwnerPtr<ICanvas> Create(UInt64 hWnd, IModelView* pModelView, ICanvas* pParentCanvas = nullptr);

#pragma region 场景构造和析构
        /// \brief 清理场景
        virtual void ClearScene() = 0;

        /// \brief 初始化Canvas场景
        ///
        /// \param pRefContext 引用的渲染上下文数据
        virtual void InitViewScene(const RenderContext* pRefContext) = 0;

        /// \brief 显示Canvas场景
        ///
        /// \param isNeedRebuildScene 是否需要重建场景
        virtual void ShowViewScene(bool isNeedRebuildScene) = 0;
        /// \brief 根据相机刷新场景里显示结点的精细程度
        virtual void RefreshSceneByCamera() = 0;

        /// \brief 反初始化Canvas场景
        virtual void UnInitViewScene() = 0;

#pragma endregion 场景构造和析构

        /// \brief 获取视图
        /// \return 视图
        virtual IModelView* GetModelView() const = 0;

        /// \brief 获取宽度
        /// \return 宽度
        virtual unsigned int GetWidth() const = 0;
        /// \brief 获取高度
        /// \return 高度
        virtual unsigned int GetHeight() const = 0;

        /// \brief 调整画布尺寸
        /// \param width 宽度
        /// \param height 高度
        virtual void Resize(unsigned int width, unsigned int height) = 0;

        /// \brief 获取相机
        /// \return 相机
        virtual const ICamera* GetCamera() const = 0;
        virtual ICamera* GetCamera() = 0;

        /// \brief 创建相机
        /// \return 相机
        virtual OwnerPtr<ICamera> CreateCamera() const = 0;

        /// \brief 获取相机事件
        /// \return 相机事件
        virtual ICameraChangedEvent* GetCameraChangedEvent() = 0;

        /// \brief 获取场景事件
        /// \return 场景事件
        virtual ISceneChangedEvent* GetSceneChangedEvent() = 0;

        /// \brief 缩放到所有场景对象可见
        virtual void ZoomAll() = 0;

        /// \brief 缩放到包围盒
        /// \param box 包围盒
        virtual void ZoomToBox(const Box3d& box) = 0;

        /// \brief 缩放到紧包围盒
        /// \param box 紧包围盒
        virtual void ZoomToTiltBox(const TiltBox3d& box) = 0;

        /// \brief 缩放到屏幕中某一矩形区域
        /// \param screenX1 屏幕上矩形区域左上角x坐标
        /// \param screenY1 屏幕上矩形区域左上角y坐标
        /// \param screenX2 屏幕上矩形区域右下角x坐标
        /// \param screenY2 屏幕上矩形区域右下角y坐标
        virtual void ZoomToRectangle(int screenX1, int screenY1, int screenX2, int screenY2) = 0;

        /// \brief 截屏并保存到文件，width和height的比例关系应该与当前窗口一致，否则较大的一边将被裁切。
        /// \param width 图片宽度
        /// \param height 图片高度
        /// \param filePath 文件路径
        /// \return 是否成功
        GCMP_OBSOLETE_API(2022/02/01, ICanvas::SaveScreenshotToFile(int,int,const std::wstring&,bool)) virtual bool SaveScreenshotToFile(int width, int height, const std::wstring& filePath) const = 0;
        
        /// \brief 截屏并保存到文件，width和height的比例关系应该与当前窗口一致，否则较大的一边将被裁切。
        /// \param width 图片宽度
        /// \param height 图片高度
        /// \param filePath 文件路径
        /// \param isBackgroundTransparent 背景是否透明
        /// \return 是否成功
        virtual bool SaveScreenshotToFile(int width, int height, const std::wstring& filePath, bool isBackgroundTransparent) const = 0;

        /// \brief 创建裁切后的屏幕截图。此接口创建的截图为高分辨率截图
        /// \param width 宽度
        /// \param height 高度
        /// \param filePath 保存的文件的全路径，其中的拓展名就是图片的文件类型。如果为空，则不进行保存
        /// \param isImageSizelimit 导出图片是否受场景视口宽高比的限制，true：是，false：否
        /// \note 当输入的宽高比与当前场景视口宽高比不一致时，输出的图片中不会包含场景视口中的全部内容。
        /// \return 是否成功
        GCMP_OBSOLETE_API(2022 / 02 / 01, ICanvas::CreateCroppedScreenshot(unsigned int, unsigned int, const std::wstring&, bool, bool))virtual bool CreateCroppedScreenshot(unsigned int width, unsigned int height, const std::wstring& filePath, bool isImageSizelimit) const = 0;
        
        /// \brief 创建裁切后的屏幕截图。此接口创建的截图为高分辨率截图
        /// \param width 宽度
        /// \param height 高度
        /// \param filePath 保存的文件的全路径，其中的拓展名就是图片的文件类型。如果为空，则不进行保存
        /// \param isImageSizelimit 导出图片是否受场景视口宽高比的限制，true：是，false：否
        /// \note 当输入的宽高比与当前场景视口宽高比不一致时，输出的图片中不会包含场景视口中的全部内容。
        /// \param isBackgroundTransparent 背景是否透明
        /// \return 是否成功
        virtual bool CreateCroppedScreenshot(unsigned int width, unsigned int height, const std::wstring& filePath, bool isImageSizelimit, bool isBackgroundTransparent) const = 0;

        /// \brief 创建裁切后的屏幕截图
        /// \ param width 宽度
        /// \ param height 高度
        /// \ param filePath 保存的文件的全路径，其中的拓展名就是图片的文件类型。如果为空，则不进行保存
        /// \ param pNewCamera 采用新的相机对场景进行截图，如果输入为null，则采用场景原来的相机进行截图
        GCMP_OBSOLETE_API(2022/02/01, ICanvas::CreateCroppedScreenshot(unsigned int, unsigned int, const std::wstring&, const ICamera*, bool))virtual void CreateCroppedScreenshot(unsigned int width, unsigned int height, const std::wstring& filePath, const ICamera* pNewCamera) const = 0;

        /// \brief 创建裁切后的屏幕截图
        /// \ param width 宽度
        /// \ param height 高度
        /// \ param filePath 保存的文件的全路径，其中的拓展名就是图片的文件类型。如果为空，则不进行保存
        /// \ param pNewCamera 采用新的相机对场景进行截图，如果输入为null，则采用场景原来的相机进行截图
        /// \ param isBackgroundTransparent 背景是否透明
        virtual void CreateCroppedScreenshot(unsigned int width, unsigned int height, const std::wstring& filePath, const ICamera* pNewCamera, bool isBackgroundTransparent) const = 0;
        
        /// \brief 创建裁切后的屏幕截图。 此接口传入一对x/y坐标，来定义截图的范围, 坐标值对应屏幕像素点的位置
        /// \param lowerLeftX 左下点的x坐标
        /// \param lowerLeftY 左下点的y坐标
        /// \param upperRightX 右上点的x坐标
        /// \param upperRightY 右上点的y坐标
        /// \param filePath filePath 保存的文件的全路径，其中的拓展名就是图片的文件类型。
        virtual void CreateRegionScreenshot(unsigned int lowerLeftX, unsigned int lowerLeftY, unsigned int upperRightX, unsigned int upperRightY, const std::wstring& filePath) const = 0;

        /// \brief 设置背景色
        /// \param color 颜色
        virtual void SetBackgroundColor(const Color& color) = 0;

        /// \brief 返回背景颜色，取的是ggp返回的四个color的第一个
        virtual Color GetBackgroundColor() const = 0;

        /// \brief 设置背景渐变色
        /// \param colorLeftUp 背景左上颜色
        /// \param colorRightUp 背景右上颜色
        /// \param colorLeftDown 背景左下颜色
        /// \param colorRightDown 背景右下颜色
        /// \return 设置成功返回true,否则返回false
        virtual bool SetBackgroundColor(const Color& colorLeftUp, const Color& colorRightUp,
            const Color& colorLeftDown, const Color& colorRightDown) = 0;

        /// \brief 设置灯光方案
        /// \param lightingScheme 灯光方案
        virtual void SetLightingScheme(LightingScheme lightingScheme) = 0;

        /// \brief 获取画布属性
        /// \return 画布属性
        virtual ICanvasOptions* GetOptions() = 0;

        /// \brief 开始录制视频
        /// 调用前先配置好动画播放器，避免开始录制不结束，原则上应注意避免多次开始录制
        /// 实现中已有对多次开始录制的保护，如多次调用，会先结束录制，再重新开始录制
        /// \param RecordSetting 录制视频设置相关参数
        /// \return 成功返回true,否则返回false
        bool virtual StartRecord(const RecordSetting& recordSetting) = 0;

        /// \brief 结束录制视频
        /// 原则上应注意避免多次结束录制
        /// 实现中已有多次结束录制的保护，多次结束相当于无效操作
        /// \return 成功返回true,否则返回false
        bool virtual EndRecord() = 0;
        
        /// \brief 在当前画布绘制离散数据
        /// \param renderLayer 渲染层次，确定与画布上其他内容的遮挡关系
        /// \param data 待绘制的离散数据
        /// \param dataCategoryUid 待绘制的离散数据的 CategoryUid
        /// \param groupId 待绘制的离散数据所在组的 Id，由调用者指定，同一组的数据会被同时删除，见 DeleteDiscretizedData
        /// \param transforms 待绘制的离散数据在场景中的位置和朝向，通过 transforms 中的变换，将 data 中的数据在场景中多次绘制
        /// \param dataId 待绘制的离散数据（data）的 Id，由调用者指定，如果 >= 0，那么 data 生成的可渲染对象会被缓存起来，如果 dataId 和 dataVersionNumber 都相同，就直接使用缓存数据，不会生成新的可渲染对象，用于优化性能。如果 < 0，每次绘制都会重新生成可渲染对象，即使 data 内容一模一样
        /// \param dataVersionNumber 待绘制的离散数据（data）的版本号，如果 dataId 相同的 data 的内容发生变化，就应该递增此值，以便更新缓存
        virtual void DrawDiscretizedData(const GraphicsRenderLayer& renderLayer
            , const IDiscretizedData& data, const UniIdentity& dataCategoryUid
            , Int64 groupId, const std::vector<Matrix4d>& transforms, Int64 dataId, UInt64 dataVersionNumber) = 0;

        /// \brief 在当前画布绘制带 lod 的离散数据
        /// \param renderLayer 渲染层次，确定与画布上其他内容的遮挡关系
        /// \param data 待绘制的离散数据，目前支持 3 层 lod，因此必须提供 3 个有效数据的指针，且各 lod 层级使用的样式和材质必须一致。lod 精度请按照从高到低排列。
        /// \param threshold 切换 lod 的阈值，也就是当离散数据的包围盒尺度在屏幕上占多少像素时切换到下一 lod 层级
        /// \param dataCategoryUid 待绘制的离散数据的 CategoryUid
        /// \param groupId 待绘制的离散数据所在组的 Id，由调用者指定，同一组的数据会被同时删除，见 DeleteDiscretizedData
        /// \param transforms 待绘制的离散数据在场景中的位置和朝向，通过 transforms 中的变换，将 data 中的数据在场景中多次绘制
        /// \param dataId 待绘制的离散数据（data）的 Id，由调用者指定，如果 >= 0，那么 data 生成的可渲染对象会被缓存起来，如果 dataId 和 dataVersionNumber 都相同，就直接使用缓存数据，不会生成新的可渲染对象，用于优化性能。如果 < 0，每次绘制都会重新生成可渲染对象，即使 data 内容一模一样
        /// \param dataVersionNumber 待绘制的离散数据（data）的版本号，如果 dataId 相同的 data 的内容发生变化，就应该递增此值，以便更新缓存
        virtual void DrawDiscretizedData(const GraphicsRenderLayer& renderLayer
            , std::vector<const IDiscretizedData*> data
            , std::vector<double> threshold, const UniIdentity& dataCategoryUid, Int64 groupId
            , const std::vector<Matrix4d>& transforms, Int64 dataId, UInt64 dataVersionNumber) = 0;

        /// \brief 在当前画布移除同一组下的所有离散数据
        /// \param groupId 绘制离散数据时指定的组 Id
        virtual void DeleteDiscretizedData(Int64 groupId) = 0;

        /// \brief 为相机事件设置相机的状态
        /// 
        /// \param cameraEventType 事件类型 例如平移事件、缩放事件、旋转事件
        /// \param isLocked 相机是否锁定 true表示相机被锁定，否则相机没被锁定，默认为false
        /// \return 设置成功返回true，否则返回false
        virtual bool SetCameraLockedForCameraEvent(CameraEventType cameraEventType, bool isLocked) = 0;

        /// \brief 获取相机的状态
        /// 
        /// \param cameraEventType 事件类型 例如平移事件、缩放事件、旋转事件
        /// \return 相机是否锁定 true表示相机被锁定，否则相机没被锁定，默认为false
        virtual bool IsCameraLockedForCameraEvent(CameraEventType eventType) const = 0;

        //////////////////////////////////////////////////////////////////////////
        // 临时添加雨雾效果，没有严格测试，仅用于试用
        /// \brief 创建雨效果(不支持平行投影相机）
        /// 透视相机下，雨的效果是静态的，只有相机发生变化或者场景节点变化才会刷新，雨滴才会动
        /// \param rainColo 雨颜色
        virtual void EnableRain(const Color& rainColor) = 0;

        /// \brief 删除雨效果
        virtual void DisableRain() = 0;

        /// \brief 是否开启与效果
        virtual bool IsRainEnabled() const = 0;

        /// \brief 创建雾效果（目前仅支持线性模式）
        /// \param fogColor 雾颜色
        /// \param density  雾密度
        /// \param start 雾影响的起始距离
        /// \param end 雾影响的终止距离
        virtual void EnableFog(const Color& fogColor, double density, double start, double end) = 0;

        /// \brief 删除雾效果
        virtual void DisableFog() = 0;

        /// \brief 是否开启雾效果
        virtual bool IsFogEnabled() const = 0;
        //////////////////////////////////////////////////////////////////////////

#pragma region 增量刷新
        /// \brief 标记场景是否需要重建
        ///
        /// \param needRebuild 场景是否需要重建
        virtual void MarkNeedRebuild(bool needRebuild) = 0;

        /// \brief 更新Canvas场景
        ///
        /// 仅监控Elements增，删和改增量刷新场景（默认调用）
        virtual void Refresh() = 0;

        /// \brief 更新该Canvas场景
        ///
        /// 可见性条件发生变化时调用（包括监控Elements增，删和改增量刷新场景，性能比Refresh差点）
        virtual void RefreshByVisibilityConditionChanged() = 0;

        /// \brief 标记视图比例发生变化，刷新关联对象（填充等）的显示
        ///
        /// \param needRefresh 视图比例相关对象是否需要刷新
        virtual void MarkNeedRefreshByViewScale(bool needRefresh) = 0;

        /// \brief 标记一下，等 Canvas 刷新的时候刷新视觉效果
        ///
        /// 没有element增删改，refresh 过程不会刷新视觉效果，比如：进入或者离开编辑模式时灰显效果的刷新
        /// 此时可调用这个标记接口，告诉框架在 Canvas 刷新过程中同时刷新视觉效果
        virtual void MarkNeedUpdateVisualEffect() = 0;

        /// \brief 立刻刷新视觉效果
        ///
        /// 除非需要立刻刷新，否则建议调用 MarkNeedUpdateVisualEffect()
        /// 没有element增删改，refresh 过程不会刷新视觉效果，比如：进入或者离开编辑模式时灰显效果的刷新
        /// 此时可调用这个接口来立刻刷新视觉效果
        virtual void UpdateVisualEffect() = 0;

        /// \brief 刷新画布（窗口）
        /// 
        /// 此函数用于触发显示引擎刷新窗口，即使场景没有任何变动，也会触发窗口刷新。
        /// 场景的变动是无法通过调用此函数来体现到窗口中，而应该调用 IUiDocument 及 IUiView 中相关接口。
        /// 如果频繁以立刻刷新的方式调用此函数可能会导致性能问题。
        /// 主要使用场景：有些功能的实现依赖于显示引擎的刷新，比如“录制视频”功能，帧录制是在显示引擎刷新过程中的，
        /// 所以在需要的时候必须调用此函数立刻刷新，否则会有漏帧的情况发生。
        /// \param immediately 是否立刻刷新，true-在函数调用处立刻刷新，false-由显示引擎的刷新频率决定刷新时机。
        virtual void RefreshRenderArea(bool immediately) = 0;

        /// \brief 标记是否需要刷新线型比例
        ///
        /// \param needRefresh 是否需要刷新线型比例
        virtual void MarkNeedRefreshLineTypeScale(bool needRefresh) = 0;
#pragma endregion 增量刷新

#pragma region 视口相关
        /// \brief 设置 subCanvas 相对于 parentCanvas 的位置，如果 parentCanvas == nullptr，那么此函数无效
        ///
        /// \param x canvas所在平面的x坐标
        /// \param y canvas所在平面的y坐标
        virtual void SetPosition(int x, int y) = 0;

        /// \brief 获取 subCanvas 相对于 parentCanvas 的位置，如果 parentCanvas == nullptr，那么 x == y == 0
        ///
        /// \param x canvas所在平面的x坐标
        /// \param y canvas所在平面的y坐标
        virtual void GetPosition(int& x, int& y) const = 0;

        /// \brief 设置最小近裁剪面
        ///
        ///由于ggp底层远近裁剪面是根据包围盒动态计算的，计算出来的近裁剪面可能接近0，
        ///这样导致整个场景的深度精度不够用，会出现Z-fighting现象, 当场景比较大时
        ///此接口是为了限制近裁剪面最小值，以便弥z补精度问题
        /// \param minNearValue   近裁剪面最小值, 默认值为0
        virtual void SetMinimalValueForCameraNearClipPlane(float minNearValue) = 0;

#pragma endregion 视口相关

        /// \brief 处理鼠标滚轮事件
        ///
        /// \param x 屏幕X坐标
        /// \param y 屏幕Y坐标
        /// \param delta 增幅
        /// \param isCtrlDown 辅助按键Ctrl是否按下
        /// \param isShiftDown 辅助按键Shift是否按下
        /// \param isAltDown 辅助按键Alt是否按下
        virtual void ProcessMouseWheelEvent(int x, int y, int delta, bool isCtrlDown, bool isShiftDown, bool isAltDown) = 0;

        /// \brief 处理鼠标中键按压事件（用于二维性能回归测试，其他情况请勿使用）
        ///
        /// \param x 屏幕X坐标
        /// \param y 屏幕Y坐标
        /// \param isCtrlDown 辅助按键Ctrl是否按下
        /// \param isShiftDown 辅助按键Shift是否按下
        /// \param isAltDown 辅助按键Alt是否按下
        virtual void ProcessMiddleButtonDownEvent(int x, int y, bool isCtrlDown, bool isShiftDown, bool isAltDown) = 0;

        /// \brief 处理鼠标中键弹起事件（用于二维性能回归测试，其他情况请勿使用）
        ///
        /// \param x 屏幕X坐标
        /// \param y 屏幕Y坐标
        /// \param isCtrlDown 辅助按键Ctrl是否按下
        /// \param isShiftDown 辅助按键Shift是否按下
        /// \param isAltDown 辅助按键Alt是否按下
        virtual void ProcessMiddleButtonUpEvent(int x, int y, bool isCtrlDown, bool isShiftDown, bool isAltDown) = 0;

        /// \brief 获取场景事件
        ///
        /// \return 返回场景事件指针，不应该为空
        virtual ICanvasEvent* GetCanvasEvent() = 0;

        /// \brief 获取退让接口的指针
        ///
        /// \return 获取退让接口的指针，不应该为空
        virtual IHaloedGapManager* GetIHaloedGapManager() = 0;

        /// \brief 将截剪区域对应的子窗口的世界坐标变换为主窗口的世界坐标
        /// \param point 截剪区域对应的子窗口的世界坐标
        ///
        /// \return 主窗口的世界坐标
        /// \note 拆分裁剪框并对其进行折叠后，鼠标拾取获取的世界坐标为子窗口的世界坐标
        virtual Vector3d MapFromSubView(const Vector3d& point) const = 0;

        /// \brief GIS数据加载完成后的回调
        /// \param callBack 回调函数
        virtual void SetGISLODLoadFinishCallBack(GISLODLoadFinishCallBack callBack) = 0;
                
        /// \brief 大场景坐标下，手动刷新显示
        /// \note  用以手动处理大坐标问题。调用此方法后，内部会将相机当前位置置为（0，0），
        ///        并将场景中所有显示对象位置减去相机所做的偏移量。
        virtual void RegenViewerForLargeWorldCoordinate() = 0;


#pragma region 动画相关
        /// \brief 进入动画模式
        ///  注：进入动画模式后
        ///   1、IColorBlend失效（灰显等）
        ///   2、若对有动画动作的构件作数据变动，则其显示不会刷新
        ///   3、轮廓线、隐藏线、遮挡退让等效果不正确，需先关闭
        ///   4、若构件有刚体变换动画，则拾取捕捉高亮可能不正确
        virtual void EnterAnimationMode() = 0;

        /// \brief 是否在动画模式下
        /// \return true：是 false：否
        virtual bool IsInAnimationMode() = 0;

        /// \brief 设置并刷新一帧动画
        /// 一帧动画包含相机状态以及各element的刚体变换、颜色、透明度、裁剪面、可见性等动作
        /// 
        /// \param pOneFrame 要刷新的一帧动画
        virtual void RefreshOneAnimationFrame(OwnerPtr<IOneAnimationFrame> opOneFrame) = 0;

        /// \brief 退出动画模式, 并恢复场景到原始状态(相机不会恢复)
        virtual void QuitAnimationMode() = 0;

#pragma endregion 动画相关

    };
}

